using System;
using System.Drawing;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;

namespace Linquid
{	
	internal class UIState
	{
		private List<Layout> layouts;
		private LayoutDirection direction = LayoutDirection.FromTop;
	
		public Font Font {get; private set;}
		
		internal LayoutDirection Direction
		{
			get {return direction;}
			set {direction = value;}
		}
		
		internal UIState(Font font): this(font, LayoutDirection.FromTop) {}
		
		internal UIState(Font font, LayoutDirection direction)
		{			
			layouts = new List<Layout>();
			this.direction = direction;
			Font = font;
		}
		
		internal void AddLayout(Layout layout)
		{
			layouts.Add(layout);		
		}				
		
		internal void Join(UIState state)
		{
			if (state.layouts.Count != 0)
			{
				AddLayout(state.Collect());
			}
		}
		
		internal void Join(UIState state, string title)
		{
			if (state.layouts.Count != 0)
			{
				AddLayout(new TitleLayout(title, Font, state.Collect()));
			}
		}
		
		internal void Group(string title)
		{
			if (layouts.Count != 0)
			{				
				var layout = new TitleLayout(title, Font, Collect());
				layouts.Clear();
				layouts.Add(layout);
			}
		}
		
		private Layout Collect()
		{
			return direction.Apply(layouts);			
		}
		
		internal void Run(Control control)
		{
			Layout.Apply(Collect(), control);
		}				
	}						
	
	/// <summary>
	/// UI action that returns value of type A.
	/// </summary>
	public class UI<A>: IEnumerable<A>
	{							
		private readonly Func<UIState, A> runUI;
				
		private UI(Func<UIState, A> runUI)
		{			
			this.runUI = runUI;
		}
		
		private static UI<B> Create<B>(Func<UIState, B> f)
		{
			return new UI<B>(f);
		}
		
		private static UI<B> Create<B>(Func<UIState, UI<B>> f)
		{
			return new UI<B>(state =>
			{
				var ui = f(state);
				var result = ui.runUI(state);
				return result;
			 });
		}
		
		/// <summary>
		/// Runs the action and apply the resulting layout to a control.
		/// </summary>
		/// <param name="control">Control to use for layout.</param>
		/// <returns>Result of the action.</returns>
		public A Run(Control control)
		{			
			var state = new UIState(control.Font);
			var result = runUI(state);
			state.Run(control);			
			return result;
		}
		
		internal static UI<Nothing> EmptySpace(Size size, float xStretch, float yStretch)
		{
			return Create(state =>
			{
				var layout = 
					new AtomicLayout(
						null, size, 
						new StretchPriority(xStretch, yStretch));
				state.AddLayout(layout);
				return Nothing.Empty;
			});
		}
		
		internal static UI<Source<C>> Insert<B, C>(
			Func<Font, B> getControl, Func<B, Source<C>> getSource, float xStretch, float yStretch) 
			where B: Control
		{
			return Create(state =>
			{
				var control = getControl(state.Font);
				var source = getSource(control);
				var layout = new AtomicLayout(
					control, control.Size, 
					new StretchPriority(xStretch, yStretch));
				state.AddLayout(layout);
				
				return source;
			});
									
		}
		
		internal static UI<Source<Nothing>> Button(string caption, float xStretch, float yStretch)
		{
			return Create(state =>
			{
				var button = new Button() 
				{
					Text = caption,					
				};				
				button.Height = (int) (button.Height * state.Font.GetHeight() / button.Font.GetHeight());
				button.Width = (int) (button.Height * 2.5);
				button.Font = state.Font;
				
				var source = new Source<Nothing>();
				button.Click += (s, a) => source.SetValue(Nothing.Empty);
				var layout = new AtomicLayout(button, button.Size, new StretchPriority(xStretch, yStretch));
				
				state.AddLayout(layout);
				return source;
			});			
		}
		
		internal static UI<Source<int>> Slider(int min, int max)
		{			
			var ui = Create(state =>
			{
				var slider = new TrackBar() 
				{
					Minimum = min, 
					Maximum = max,				
				};
				slider.Value = min;
				var source = new Source<int>(slider.Value);
				slider.ValueChanged += (s, a) => source.SetValue(slider.Value);
				var layout = new AtomicLayout(slider, slider.Size, new StretchPriority(1.0f, 0.0f));
				state.AddLayout(layout);
				return source;
			});
			return
				from s in ui
				from _ in UIM.Output(s)
				orderby LayoutDirection.FromRight
				select s;							
		}				
		
		internal static UI<Source<B>> Input<B>(Func<string, B> parse, string init, float stretch)
		{			
			return Create(state =>
			{
				var input = new TextBox() 
				{
					Text = init,
					Font = state.Font					
				};
				
				var source = new Source<string>(init);
				
				input.KeyUp += (s, a) => source.SetValue(input.Text);
			
				var layout = new AtomicLayout(input, input.Size, new StretchPriority(stretch, 0.0f));			
			
				state.AddLayout(layout);
				return source.Map(parse);
			});			
		}
		
		internal static UI<Nothing> Output<B>(Source<B> source, float stretch)
		{			
			return Create(state =>
			{
				var output = new TextBox() 
				{
					ReadOnly = true,					
					Font = state.Font					
				};
				output.Width = output.Height * 2;
				
				foreach (var value in source)									
				{
					output.Text = value.ToString();
				}
				
				var layout = new AtomicLayout(output, output.Size, new StretchPriority(stretch, 0.0f));
				source.ValueChanged += 
					obj => {output.Text = obj.ToString();};
				state.AddLayout(layout);
				
				return Nothing.Empty;
			});			
		}
		
		internal static UI<Source<B>> Choice<B>(B defaultChoice, params B[] choices)
		{		
			return Create(state =>
			{
				var box = new ComboBox()
				{
					Font = state.Font,
					Height = int.MaxValue
						
				};
				foreach (var item in choices)
				{
					box.Items.Add((object) item);
				}
				if (choices.Contains(defaultChoice))
				{
					box.SelectedItem = defaultChoice;
				}
				var layout = new AtomicLayout(box);
				var source = new Source<B>(defaultChoice);
				box.SelectedValueChanged += (s, a) => source.SetValue((B)box.SelectedItem);
				state.AddLayout(layout);
				
				return source;
			});			
		}
		
		/// <summary>
		/// Applies given function to the action's result.
		/// </summary>
		/// <param name="f">Function to apply.</param>		
		public UI<B> Map<B>(Func<A, B> f)
		{
			return Create(state =>
			{
				var result = runUI(state);
				return f(result);
			});
		}				
					
		///<summary>Method used in LINQ queries.</summary>
		public UI<C> SelectMany<B, C>(Func<A, UI<B>> f,
		                              Func<A, B, C> ff)
		{					
			return Create(state =>
			{
			    var result = runUI(state);			              	
			    var ui = f(result);
			    return ff(result, ui.runUI(state));
			});			
		}
		
		///<summary>Method used in LINQ queries.</summary>
		public UI<B> SelectMany<B>(Func<A, UI<B>> f)
		{					
			return Bind(f);
		}
		
		///<summary>Method used in LINQ queries.</summary>
		public UI<B> Select<B>(Func<A, B> f)		
		{
			return Map(f);
		}				
		
		///<summary>Method used in LINQ queries.</summary>
		public UI<B> GroupBy<B>(Func<A, string> f,  Func<A, B> ff)
		{						
			return Create(state =>
			{
			    var s = new UIState(state.Font);
				var result = runUI(s);
				var title = f(result);
				state.Join(s, title);
				return Return(ff(result)).runUI(state);
			});			
		}
		
		///<summary>Method used in LINQ queries.</summary>
		public UI<A> GroupBy(Func<A, string> f)
		{			
			return Create(state =>
			{
			    var s = new UIState(state.Font);
				var result = runUI(s);
				var title = f(result);
				state.Join(s, title);
				return result;
			});		
		}
		
		///<summary>Method used in LINQ queries.</summary>
		public UI<A> OrderBy(Func<A, LayoutDirection> f)
		{			
			Func<UIState, A> run = state =>
			{
				var s = new UIState(state.Font);
				var result = runUI(s);
				s.Direction = f(result);
				state.Join(s);
				return result;
			};
			return new UI<A>(run);
		}				
		
		/// <summary>
		/// Order controls using the given layout.
		/// </summary>
		public UI<A> OrderBy(LayoutDirection direction)
		{
			Func<UIState, A> run = state =>
			{
				var s = new UIState(state.Font, direction);
				var result = runUI(s);
				state.Join(s);
				return result;
			};
			return new UI<A>(run);
		}

		/// <summary>
		/// Layout controls from left to right.
		/// </summary>
		public UI<A> FromLeft()
		{
			return OrderBy(LayoutDirection.FromLeft);
		}
		
		/// <summary>
		/// Layout controls from top to bottom.
		/// </summary>
		public UI<A> FromTop()
		{
			return OrderBy(LayoutDirection.FromTop);
		}
		
		/// <summary>
		/// Layout controls from right to left.
		/// </summary>
		public UI<A> FromRight()
		{
			return OrderBy(LayoutDirection.FromRight);
		}
		
		/// <summary>
		/// Layout controls from bottom to top.
		/// </summary>
		public UI<A> FromBottom()
		{
			return OrderBy(LayoutDirection.FromTop);
		}				
		
		
		/// <summary>
		/// Group the controls produced by the action under the specified title.
		/// </summary>
		/// <param name="title">String to title the group with.</param>
		public UI<A> Title(string title)
		{
			Func<UIState, A> run = state =>
			{
				var s = new UIState(state.Font);
				var result = runUI(s);				
				state.Join(s, title);
				return result;
			};
			
			return new UI<A>(run);
		}
						
		/// <summary>
		/// Replace current action's result with given value.
		/// </summary>		
		public UI<B> Return<B>(B value)
		{						
			return new UI<B>(s => value);
		}		
		
		
		/// <summary>
		/// Combines UI actions.
		/// </summary>
		public UI<B> Bind<B>(UI<B> ui)
		{
			return new UI<B>(state => 
			                 {runUI(state); return ui.runUI(state);});
		}		

		/// <summary>
		/// Combines UI actions.
		/// </summary>
		/// <param name="f">Function thas takes the result of the
		/// current action and produces another action used for
		/// combination.</param>
		public UI<B> Bind<B>(Func<A, UI<B>> f)
		{
			return Create(state =>
			{				
				return f(runUI(state)).runUI(state);
			});
		}	
		
		/// <summary>
		/// Has no meaning for this type.
		/// </summary>		
		public IEnumerator<A> GetEnumerator()
		{
			throw new NotImplementedException();
		}
		
		IEnumerator IEnumerable.GetEnumerator()
		{
			throw new NotImplementedException();
		}
	}				
}
